﻿using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace suirui.ZhuMu.Common
{
    /// <summary>
    /// 第三方数据存储和获取的地方
    /// 
    /// 过期策略：
    ///     永久有效
    ///     绝对过期---有个时间点，超过就过期
    ///     滑动过期---多久之后过期，如果期间更新/查询/检查存在，就再次延长
    /// 
    /// 主动清理+被动清理，保证过期数据不会被查询；过期数据也不会滞留太久
    /// 
    /// 多线程操作非线程安全的容器，会造成冲突，那有什么解决方案呢？
    ///     1、使用线程安全容器ConcurrentDictionary
    ///     2、用lock---Add/Remove/遍历 解决问题了，但是性能怎么办呢？
    ///     怎么降低影响，提升性能呢？ --- 数据分拆，多个数据容器，多个锁，容器之间可以并发。
    /// </summary>
    public class CustomCache
    {
        #region 字段和属性

        /// <summary>
        /// 模拟获取系统的CPU数
        /// </summary>
        private static int _cpuNumer = 0;

        /// <summary>
        /// 动态初始化多个容器
        /// </summary>
        private static List<Dictionary<string, object>> _dictionaryList = new List<Dictionary<string, object>>();

        /// <summary>
        /// 动态初始化多个锁
        /// </summary>
        private static List<object> _lockList = new List<object>();

        #endregion 字段和属性

        #region 静态构造函数

        /// <summary>
        /// 静态构造函数
        /// </summary>
        static CustomCache()
        {
            _cpuNumer = 4;
            for (int i = 0; i < _cpuNumer; i++)
            {
                _dictionaryList.Add(new Dictionary<string, object>());
                _lockList.Add(new object());
            }

            //主动清理缓存
            Task.Run(() =>
            {
                while (true)
                {
                    Thread.Sleep(1000 * 60 * 10);
                    try
                    {
                        for (int i = 0; i < _cpuNumer; i++)
                        {
                            List<string> keyList = new List<string>();
                            lock (_lockList[i]) //数据分拆，减少锁的影响范围
                            {
                                foreach (var key in _dictionaryList[i].Keys)
                                {
                                    DataModel model = (DataModel)_dictionaryList[i][key];
                                    if (model.ObsloteType != ObsloteType.Never && model.DeadLine < DateTime.Now)
                                    {
                                        keyList.Add(key);
                                    }
                                }
                                keyList.ForEach(s => _dictionaryList[i].Remove(s));
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.ToString());
                        continue;
                    }
                }
            });
        }

        #endregion 静态构造函数

        /// <summary>
        /// 获取容器索引
        /// </summary>
        /// <param name="key">缓存键</param>
        /// <returns>索引值</returns>
        public static int GetHashCodeIndex(string key)
        {
            int hash = Math.Abs(key.GetHashCode()); //相对均匀而且稳定
            return hash % _cpuNumer;
        }

        /// <summary>
        /// 添加
        /// </summary>
        /// <param name="key">缓存键</param>
        /// <param name="oValue">缓存值</param>
        public static void Add(string key, object oValue)
        {
            int index = GetHashCodeIndex(key);
            lock (_lockList[index])
            {
                _dictionaryList[index].Add(key, new DataModel()
                {
                    Value = oValue,
                    ObsloteType = ObsloteType.Never
                });
            }
        }

        /// <summary>
        /// 绝对过期
        /// </summary>
        /// <param name="key">缓存键</param>
        /// <param name="oVaule">缓存值</param>
        /// <param name="timeOutSecond">过期时间</param>
        public static void Add(string key, object oVaule, int timeOutSecond)
        {
            int index = GetHashCodeIndex(key);
            lock (_lockList[index])
            {
                _dictionaryList[index].Add(key, new DataModel()
                {
                    Value = oVaule,
                    ObsloteType = ObsloteType.Absolutely,
                    DeadLine = DateTime.Now.AddSeconds(timeOutSecond)
                });
            }
        }

        /// <summary>
        /// 相对过期
        /// </summary>
        /// <param name="key">缓存键</param>
        /// <param name="oVaule">缓存值</param>
        /// <param name="duration">过期时间</param>
        public static void Add(string key, object oVaule, TimeSpan duration)
        {
            int index = GetHashCodeIndex(key);
            lock (_lockList[index])
            {
                _dictionaryList[index].Add(key, new DataModel()
                {
                    Value = oVaule,
                    ObsloteType = ObsloteType.Relative,
                    DeadLine = DateTime.Now.Add(duration),
                    Duration = duration
                });
            }
        }

        /// <summary>
        /// 获取数据（要求在Get前做Exists检测）
        /// </summary>
        /// <typeparam name="T">类型</typeparam>
        /// <param name="key">缓存键</param>
        /// <returns>缓存值</returns>
        public static T Get<T>(string key)
        {
            int index = GetHashCodeIndex(key);
            return (T)((DataModel)_dictionaryList[index][key]).Value;
        }

        /// <summary>
        /// 判断缓存是否存在（被动清理，请求了数据，才能清理）
        /// </summary>
        /// <param name="key">缓存键</param>
        /// <returns></returns>
        public static bool Exists(string key)
        {
            int index = GetHashCodeIndex(key);
            if (_dictionaryList[index].ContainsKey(key))
            {
                DataModel model = (DataModel)_dictionaryList[index][key];
                if (model.ObsloteType == ObsloteType.Never)
                {
                    return true;
                }
                else if (model.DeadLine < DateTime.Now) //现在已经超过你的最后时间
                {
                    lock (_lockList[index])
                    {
                        //被动清理，请求了数据，才能清理
                        _dictionaryList[index].Remove(key);
                    }

                    return false;
                }
                else
                {
                    if (model.ObsloteType == ObsloteType.Relative) //没有过期&是滑动 所以要更新
                    {
                        model.DeadLine = DateTime.Now.Add(model.Duration);
                    }

                    return true;
                }
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// 移除缓存
        /// </summary>
        /// <param name="key">缓存键</param>
        public static void Remove(string key)
        {
            int index = GetHashCodeIndex(key);
            lock (_lockList[index])
            {
                _dictionaryList[index].Remove(key);
            }
        }

        /// <summary>
        /// 移除所有缓存
        /// </summary>
        public static void RemoveAll()
        {
            for (int index = 0; index < _cpuNumer; index++)
            {
                lock (_lockList[index])
                {
                    _dictionaryList[index].Clear();
                }
            }
        }

        /// <summary>
        /// 按条件移除
        /// </summary>
        /// <param name="func">条件表达式</param>
        public static void RemoveCondition(Func<string, bool> func)
        {
            for (int i = 0; i < _cpuNumer; i++)
            {
                List<string> keyList = new List<string>();
                lock (_lockList[i])
                {
                    foreach (var key in _dictionaryList[i].Keys)
                    {
                        if (func.Invoke(key))
                        {
                            keyList.Add(key);
                        }
                    }
                    keyList.ForEach(s => Remove(s));
                }
            }
        }

        public static T GetT<T>(string key, Func<T> func)
        {
            T t;
            if (!Exists(key))
            {
                t = func.Invoke();
                Add(key, t);
            }
            else
            {
                t = Get<T>(key);
            }

            return t;
        }
    }

    /// <summary>
    /// 缓存的信息
    /// </summary>
    internal class DataModel
    {
        public object Value { get; set; }
        public ObsloteType ObsloteType { get; set; }
        public DateTime DeadLine { get; set; }
        public TimeSpan Duration { get; set; }

        /// <summary>
        /// 数据清理后出发事件
        /// </summary>
        public event Action DataClearEvent;
    }

    /// <summary>
    /// 缓存策略
    /// </summary>
    public enum ObsloteType
    {
        /// <summary>
        /// 永久
        /// </summary>
        Never,

        /// <summary>
        /// 绝对过期
        /// </summary>
        Absolutely,

        /// <summary>
        /// 相对过期
        /// </summary>
        Relative
    }


    public class TestCustomCache
    {
        public void Test()
        {
            try
            {
                {
                    //多线程问题
                    List<Task> taskList = new List<Task>();
                    for (int i = 0; i < 1_000_000; i++)
                    {
                        int k = i;
                        taskList.Add(Task.Run(() => CustomCache.Add($"TestKey_{k}", $"TestValue_{k}", 10)));
                    }

                    for (int i = 0; i < 100; i++)
                    {
                        int k = i;
                        taskList.Add(Task.Run(() =>
                        {
                            if (k % 10 == 0)
                            {
                                Console.WriteLine(CustomCache.Get<string>($"TestKey_{k}"));
                            }

                            CustomCache.Remove($"TestKey_{k}");

                            if (k % 10 == 0)
                            {
                                Console.WriteLine($"TestKey_{k}_Exists:{CustomCache.Exists($"TestKey_{k}")}");
                            }
                        }));
                    }

                    for (int i = 0; i < 100; i++)
                    {
                        int k = i;
                        taskList.Add(Task.Run(() =>
                        {
                            CustomCache.Exists($"TestKey_{k}");
                        }));
                    }

                    Task.WaitAll(taskList.ToArray());
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }

            Console.ReadKey();

        }
    }
}
